﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;

using Caliburn.Micro;

using Csla.Xaml;

using Action = System.Action;

namespace Haushaltsbuch.Extensions
{
    public abstract class CslaViewAware<T> : ViewModelBase<T>, IViewAware, INotifyPropertyChangedEx, INotifyPropertyChanged
    {
        private static readonly DependencyProperty PreviouslyAttachedProperty =
            DependencyProperty.RegisterAttached("PreviouslyAttached", typeof(bool), typeof(CslaViewAware<T>), null);

        /// <summary>
        ///     Indicates whether or not implementors of <see cref="T:Caliburn.Micro.IViewAware" /> should cache their views by default.
        /// </summary>
        public static bool CacheViewsByDefault = true;

        /// <summary>
        ///     The view chache for this instance.
        /// </summary>
        protected readonly Dictionary<object, object> Views = new Dictionary<object, object>();

        private bool cacheViews;

        /// <summary>
        ///     Indicates whether or not this instance maintains a view cache.
        /// </summary>
        protected bool CacheViews
        {
            get { return this.cacheViews; }
            set
            {
                this.cacheViews = value;
                if (this.cacheViews)
                    return;
                this.Views.Clear();
            }
        }

        /// <summary>
        ///     Creates an instance of <see cref="T:Caliburn.Micro.ViewAware" />.
        /// </summary>
        public CslaViewAware() : this(CacheViewsByDefault) {}

        /// <summary>
        ///     Creates an instance of <see cref="T:Caliburn.Micro.ViewAware" />.
        /// </summary>
        /// <param name="cacheViews">Indicates whether or not this instance maintains a view cache.</param>
        public CslaViewAware(bool cacheViews)
        {
            this.CacheViews = cacheViews;
        }

        /// <summary>
        ///     Called when a view is attached.
        /// </summary>
        /// <param name="view">The view.</param>
        /// <param name="context">The context in which the view appears.</param>
        protected internal virtual void OnViewAttached(object view, object context) {}

        /// <summary>
        ///     Called when an attached view's Loaded event fires.
        /// </summary>
        /// <param name="view" />
        protected internal virtual void OnViewLoaded(object view) {}

        #region IViewAware Members

        /// <summary>
        ///     Raised when a view is attached.
        /// </summary>
        public event EventHandler<ViewAttachedEventArgs> ViewAttached = delegate { };

        void IViewAware.AttachView(object view, object context)
        {
            if (this.CacheViews)
                this.Views[context ?? View.DefaultContext] = view;
            object view1 = View.GetFirstNonGeneratedView(view);
            var element = view1 as FrameworkElement;
            if (element != null && !(bool)element.GetValue(PreviouslyAttachedProperty))
            {
                element.SetValue(PreviouslyAttachedProperty, true);
                View.ExecuteOnLoad(element, ((s, e) => this.OnViewLoaded(s)));
            }
            this.OnViewAttached(view1, context);
            this.ViewAttached(this, new ViewAttachedEventArgs
                {
                    View = view1,
                    Context = context
                });
        }

        /// <summary>
        ///     Gets a view previously attached to this instance.
        /// </summary>
        /// <param name="context">The context denoting which view to retrieve.</param>
        /// <returns>
        ///     The view.
        /// </returns>
        public virtual object GetView(object context = null)
        {
            object obj;
            this.Views.TryGetValue(context ?? View.DefaultContext, out obj);
            return obj;
        }

        #endregion

        public void NotifyOfPropertyChange(string propertyName)
        {
            if (!this.IsNotifying)
                return;

            ((Action)(() => this.OnPropertyChanged(propertyName))).OnUIThread();
        }

        /// <summary>
        /// Notifies subscribers of the property change.
        /// 
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam><param name="property">The property expression.</param>
        public virtual void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
        {
            this.NotifyOfPropertyChange(property.GetMemberInfo().Name);
        }


        public void Refresh()
        {
            this.NotifyOfPropertyChange(string.Empty);
        }

        [NonSerialized]
        private bool isNotifying;

        /// <summary>
        /// Enables/Disables property change notification.
        /// 
        /// </summary>
        public bool IsNotifying
        {
            get
            {
                return this.isNotifying;
            }
            set
            {
                this.isNotifying = value;
            }
        }

        /// <summary>
        /// Raises the property changed event immediately.
        /// 
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        public virtual void RaisePropertyChangedEventImmediately(string propertyName)
        {
            if (!this.IsNotifying)
                return;
            this.OnPropertyChanged(propertyName);
        }
    }
}